
#include "web.hpp"

#include <ArduinoJson.h>
#include <ESP8266HTTPClient.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <LittleFS.h>
#include <NTPClient.h>
#include <WiFiUdp.h>

#include <memory>

#include "io_expansion.hpp"
#include "oled.hpp"
#include "scope_guard.hpp"
#include "zou.hpp"


static AsyncWebServer server(80);  // 设置服务器端口

static WiFiUDP udp_socket;                                                    // 声明UDP
static NTPClient timeClient(udp_socket, "ntp1.aliyun.com", 8 * 3600, 60000);  // 配置NTP服务器

static WiFiClient tcp_client;   // 创建TCP客户端
static HTTPClient http_client;  // 初始化HTTP客户端

static bool ntp_task_ready = false;

constexpr const char *weatherAPI = "http://api.seniverse.com/v3/weather/daily.json?key=";  // 心知天气API地址

static String ssid = "";
static String pass = "";

static String weather_api_url = "";
static String city = "";
static String api_key = "";

static bool weather_task_ready = false;

static String temperature = "NaN";
static String humidity = "NaN";
static String weather = "NaN";

static int ntp_hour = -1;
static int ntp_minute = -1;


static motion_request _motion_requested = motion_request::none;
static bool _motion_rejected = false;

static display_request _display_requested = display_request::none;

static display_request _current_display_mode = display_request::ip;

/**
 * @brief 查询并清零标志
 *
 * @return true
 * @return false
 */
bool motion_rejected() {
    bool f = _motion_rejected;
    _motion_rejected = false;
    return f;
}

/**
 * @brief 查询并清零标志
 *
 * @return motion_request
 */
motion_request motion_requested() {
    auto r = _motion_requested;
    _motion_requested = motion_request::none;
    return r;
}


display_request display_requested() {
    auto r = _display_requested;
    _display_requested = display_request::none;
    return r;
}

display_request current_display() {
    return _current_display_mode;
}

void set_current_display(display_request m) {
    _current_display_mode = m;
}


static void handle_motion_request(AsyncWebServerRequest *request, motion_request req) {
    // 如果正有动作在执行，就拒绝新的请求
    if (req != motion_request::no_free && !motion_completed()) {
        _motion_rejected = true;
        request->send(200, "text/plain", "NO");
    }
    else {
        _motion_rejected = false;
        _motion_requested = req;
        request->send(200, "text/plain", "OK");
    }
}


static void handle_display_request(AsyncWebServerRequest *request, display_request req) {
    _display_requested = req;
    _current_display_mode = req;
    request->send(200, "text/plain", "OK");
}


static void handle_page_request(AsyncWebServerRequest *request, const char *path) {
    // 检查SPIFFS文件系统中是否存在index.html文件
    if (LittleFS.exists(path)) {
        fs::File file = LittleFS.open(path, "r");  // 打开index.html文件
        if (file) {
            request->send(LittleFS, path, "text/html");
            return;
        }
    }

    // 如果文件不存在或打开失败，返回404错误
    request->send(404, "text/plain", "File Not Found");
}


void init_server() {
    server.on("/front", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_motion_request(request, motion_request::front);
    });

    server.on("/back", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_motion_request(request, motion_request::back);
    });

    server.on("/left", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_motion_request(request, motion_request::left);
    });

    server.on("/right", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_motion_request(request, motion_request::right);
    });

    server.on("/toplefthand", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_motion_request(request, motion_request::left_hand);
    });

    server.on("/toprighthand", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_motion_request(request, motion_request::right_hand);
    });

    server.on("/sitdown", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_motion_request(request, motion_request::sit_down);
    });

    server.on("/lie", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_motion_request(request, motion_request::lie_down);
    });

    server.on("/sleep", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_motion_request(request, motion_request::sleep);
    });

    server.on("/free", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_motion_request(request, motion_request::free);
    });

    server.on("/offfree", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_motion_request(request, motion_request::no_free);
    });

    server.on("/shake", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_motion_request(request, motion_request::shake);
    });

    server.on("/test", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_motion_request(request, motion_request::test);
    });

    server.on("/histate", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_display_request(request, display_request::hi);
    });

    server.on("/angrystate", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_display_request(request, display_request::angry);
    });

    server.on("/edastate", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_display_request(request, display_request::eda);
    });

    server.on("/errorstate", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_display_request(request, display_request::err);
    });

    server.on("/batteryVoltage", HTTP_GET, [](AsyncWebServerRequest *request) {
        request->send(200, "text/plain", String(battery_voltage()));
    });

    server.on("/batteryPercentage", HTTP_GET, [](AsyncWebServerRequest *request) {
        request->send(200, "text/plain", String(map_battery_percentage(battery_voltage())));
    });

    server.on("/dowhatstate", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_display_request(request, display_request::dowhat);
    });

    server.on("/lovestate", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_display_request(request, display_request::love);
    });

    server.on("/sickstate", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_display_request(request, display_request::sick);
    });

    server.on("/yunstate", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_display_request(request, display_request::yun);
    });

    server.on("/time", HTTP_GET, [](AsyncWebServerRequest *request) {
        // 如果ntp 更新任务未启动，显示error
        if (!ntp_task_ready) {
            handle_display_request(request, display_request::err);
        }
        handle_display_request(request, display_request::time);
    });

    server.on("/weather", HTTP_GET, [](AsyncWebServerRequest *request) {
        // 如果天气更新任务未启动，显示error
        if (!weather_task_ready) {
            handle_display_request(request, display_request::err);
        }
        handle_display_request(request, display_request::weather);
    });

    server.on("/connect", HTTP_POST, [](AsyncWebServerRequest *request) {
        // 获取POST参数：ssid、pass、city、api
        ssid.clear();
        pass.clear();
        city.clear();
        api_key.clear();
        weather_api_url.clear();

        ssid = request->getParam("ssid", true)->value();
        pass = request->getParam("pass", true)->value();
        city = request->getParam("city", true)->value();
        api_key = request->getParam("api", true)->value();
        weather_api_url += weatherAPI;
        weather_api_url += "&location=";
        weather_api_url += city;
        weather_api_url += "&language=zh-Hans&unit=c&start=0&days=1";

        if (!weather_task_ready && (api_key.length() > 0 && city.length() > 0)) {
            // 如果天气更新任务之前没启动，则在此时启动
            extern TimeType task_update_weather();
            delay_callback.add_task(task_update_weather, 2000);  // 延时两秒后运行，等待WIFI 连接上
            weather_task_ready = true;
        }

        if (!ntp_task_ready) {
            extern TimeType task_update_ntp();
            delay_callback.add_task(task_update_ntp, 2000);
            ntp_task_ready = true;
        }

        auto doc = JsonDocument{};
        doc["ssid"] = ssid;
        doc["pass"] = pass;
        doc["city"] = city;
        doc["api"] = api_key;

        // 打印接收到的参数
        serial_log.print("SSID = ");
        serial_log.println(request->getParam("ssid", true)->value());
        serial_log.print("CODE = ");
        serial_log.println(request->getParam("pass", true)->value());

        // 保存WiFi信息到MessagePack
        fs::File file = LittleFS.open(APP_PARAM_PATH, "w");
        if (file) {
            serializeMsgPack(doc, file);
            file.close();
        }

        // 开始连接WiFi
        extern void try_connect_wifi();
        try_connect_wifi();
        // 发送HTML响应，告知用户正在连接
        // 发送带UTF-8编码声明的HTML响应
        request->send(
            200,
            "text/html; charset=UTF-8",
            "<!DOCTYPE html>"
            "<html>"
            "<head>"
            "    <meta charset='UTF-8'>"
            "    <title>状态</title>"
            "</head>"
            "<body>"
            "    <h1>请返回使用在线功能，如果能正常获取则配置成功！</h1>"
            "</body>"
            "</html>");
    });

    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_page_request(request, HTML_INDEX_PATH);
        // DEBUG
        serial_log.println("REQUEST INDEX");
    });

    server.on("/control.html", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_page_request(request, HTML_CONTROL_PATH);
    });

    // server.on("/engine.html", HTTP_GET, [](AsyncWebServerRequest *request) {
    //     //
    // });

    server.on("/setting.html", HTTP_GET, [](AsyncWebServerRequest *request) {
        handle_page_request(request, HTML_SETTING_PATH);
    });

    // 启动服务器
    server.begin();
};


// =================== NTP Client ==================

void init_ntp_client() {
    timeClient.begin();
}


/**
 * @brief 每秒更新一次NTP Client，如果当前显示模式为time，则同时更新显示
 *
 * @return TimeType
 */
TimeType task_update_ntp() {
    if (WiFi.isConnected()) {
        timeClient.update();
        ntp_hour = timeClient.getHours();
        ntp_minute = timeClient.getMinutes();
    }
    else {
        ntp_hour = -1;
        ntp_minute = -1;
    }

    if (_current_display_mode == display_request::time) {
        draw_time(ntp_hour, ntp_minute);
    }
    return 1000;
}


// =================== Weather ==================

TimeType task_update_weather() {
    // 默认更新周期为一小时，或3600'000 毫秒
    constexpr TimeType DELAY_RETRY = 5'000;
    constexpr TimeType DELAY_NORMAL = 3600'000;

    static int failure_counter = 0;

    if (api_key.length() == 0 || city.length() == 0) {
        // 如果API 不可用，任务自动删除
        weather_task_ready = false;
        return 0;
    }

    TimeType return_value = DELAY_NORMAL;

    do {
        if (!WiFi.isConnected()) {
            // 如果网络不可用，下一次重试的间隔为5 秒.
            return_value = DELAY_RETRY;
            break;
        }

        serial_log.print("Fetching weather\n");
        // 发起HTTP连接
        bool flag = http_client.begin(tcp_client, weather_api_url);
        if (!flag) {
            serial_log.println("服务器连接失败，请检查API地址");
            return_value = DELAY_RETRY;
            break;
        }

        // 确保退出函数时关闭连接
        ScopeGuard to_end_http_client{
            []() {
                http_client.end();
            }};

        int httpCode = http_client.GET();
        // 成功接收响应（httpCode 200表示成功）
        if (httpCode != 200) {
            serial_log.printf("HTTP请求失败，错误代码: %d，详情: %s\n",
                              httpCode, http_client.errorToString(httpCode).c_str());
            // API 请求失败或许是因为请求太频繁
            break;
        }

        serial_log.print("Weather api response ok\n");
        const String &payload = http_client.getString();  // 获取完整响应数据


        if (payload.length() < 10) {
            serial_log.print("Invalid payload: ");
            serial_log.println(payload.c_str());
            break;
        }

        // 创建JSON文档并解析数据
        JsonDocument doc;
        auto error = deserializeJson(doc, payload);

        if (error) {
            serial_log.println("JSON解析失败: " + String(error.c_str()));
            break;
        }

        const auto &&rt = doc["results"][0]["daily"][0]["high"];      // 最高温度
        const auto &&rh = doc["results"][0]["daily"][0]["humidity"];  // 湿度值
        const auto &&rw = doc["results"][0]["daily"][0]["text_day"];  // 天气

        if (rt.isNull() || rh.isNull() || rw.isNull()) {
            serial_log.println("Invalid field");
            break;
        }

        // 更新全局天气变量
        temperature = rt.as<const char *>();
        humidity = rh.as<const char *>();
        weather = rw.as<const char *>();

        failure_counter = 0;
        // 调试输出解析结果
        serial_log.print("Weather: ");
        serial_log.println(weather);
        serial_log.print("Temp: ");
        serial_log.println(temperature);
        serial_log.print("Humidity: ");
        serial_log.println(humidity);

        // 如果当前在显示天气，就更新显示
        if (_current_display_mode == display_request::weather) {
            draw_weather(weather, temperature, humidity);
        }
    } while (0);

    // 如果多次重试都没有成功获取天气信息，就把全局变量设置为NAN
    ++failure_counter;
    if (failure_counter > 60) {
        serial_log.println("Weather update timeout");
        weather = "NAN";
        temperature = "NAN";
        humidity = "NAN";
        failure_counter = 0;
    }
    return return_value;
}


void display_weather() {
    draw_weather(weather, temperature, humidity);
}


void display_time() {
    draw_time(ntp_hour, ntp_minute);
}

void display_control_ip() {
    auto m = WiFi.getMode();
    const char *s = nullptr;
    IPAddress ip;
    if (m == WIFI_AP) {
        s = "AP MODE";
        ip = WiFi.softAPIP();
    }
    else if (m == WIFI_STA || m == WIFI_AP_STA) {
        s = "STA MODE";
        ip = WiFi.localIP();
    }
    else {
        s = "UNKNOW ERR";
    }

    draw_wifi_control(s, ip);
}

// =================== Connection ==================

/**
 * @brief 持续监视WIFI 连接状态，当网络状态异常时，显示个提示
 *
 */
TimeType task_wifi_connection_monitor() {
    // 定时任务里检查连接状态，连接ok 后自动删除任务
    auto m = WiFi.getMode();
    if (m == WIFI_AP) {
        serial_log.println("WIFI AP MODE");
    }
    else if (m == WIFI_STA || m == WIFI_AP_STA) {
        serial_log.println("WIFI STA MODE");

        if (WiFi.isConnected()) {
            serial_log.println(WiFi.localIP());
        }
        else {
            // TODO: 刷新状态显示
            auto e = WiFi.status();
            switch (e) {
                case WL_NO_SSID_AVAIL:
                    serial_log.print("SSID = ");
                    serial_log.print(ssid);
                    serial_log.println(", Not Found");
                    break;

                case WL_CONNECT_FAILED:
                    serial_log.print("SSID = ");
                    serial_log.print(ssid);
                    serial_log.println(", Connect Failed");
                    break;

                case WL_WRONG_PASSWORD:
                    serial_log.print("PASS = ");
                    serial_log.print(pass);
                    serial_log.println(", Wrong Code");
                    break;

                default:
                    serial_log.println("Disconnected");
                    break;
            }
        }
    }
    else {
        serial_log.println("WIFI ERR MODE");
    }
    return 10'000;
}


void start_ap() {
    WiFi.softAP(AP_SSID, AP_PASSWORD);
    serial_log.println("AP MODE");
    serial_log.print("SSID: ");
    serial_log.println(AP_SSID);
    // 访问的IP地址是 ESP8266 的默认IP：192.168.4.1
    serial_log.print("APIP = ");
    serial_log.println(WiFi.softAPIP());
}


/**
 * @brief
 *
 * @return true  配置已导入
 * @return false 不存在有效的配置文件
 */
bool load_param() {
    // 尝试打开存储WiFi配置的JSON文件（需提前创建）
    fs::File file = LittleFS.open(APP_PARAM_PATH, "r");
    if (file) {
        JsonDocument doc;
        auto error = deserializeMsgPack(doc, file);
        bool ret = false;

        do {
            if (error) {
                break;
            }

            // 从JSON对象中提取配置参数
            auto &&param_ssid = doc["ssid"];  // WiFi名称字段
            auto &&param_pass = doc["pass"];  // WiFi密码字段
            auto &&param_city = doc["city"];  // 城市代码字段
            auto &&param_api = doc["api"];    // 天气API密钥字段

            if (param_ssid.isNull() || param_pass.isNull() || param_city.isNull() || param_api.isNull()) {
                break;
            }

            // 将配置参数赋给全局变量
            ssid = param_ssid.as<const char *>();
            pass = param_pass.as<const char *>();
            city = param_city.as<const char *>();
            api_key = param_api.as<const char *>();

            serial_log.println("Param file loaded");
            serial_log.print("SSID = ");
            serial_log.println(ssid);
            serial_log.print("CODE = ");
            serial_log.println(pass);
            ret = true;
        } while (0);

        file.close();  // 关闭文件释放资源
        return ret;
    }
    else {
        return false;
    }
}


void try_connect_wifi() {
    using DCB = decltype(delay_callback);
    static auto task_index = DCB::INVALID_INDEX;
    // 如果index 有效，说明之前添加的延时任务还没结束，
    // 不需要重复尝试连接
    if (task_index == DCB::INVALID_INDEX) {
        // 使用存储的凭证尝试连接WiFi
        WiFi.softAPdisconnect(true);
        WiFi.begin(ssid.c_str(), pass.c_str());  // 转换为C风格字符串
        serial_log.println("Try connect");
        // 如果10 秒后仍未连接成功，就切换为AP 模式
        auto connection_check = []() -> TimeType {
            if (!WiFi.isConnected()) {
                start_ap();
            }
            else {
                serial_log.println("Connected");
            }
            task_index = DCB::INVALID_INDEX;
            return 0;
        };
        task_index = delay_callback.add_task(connection_check, 10'000);
    }
}


TimeType task_show_ip() {
    _display_requested = display_request::ip;
    return 0;
}


/**
 * @brief 初始化整个网络模块
 *
 */
void init_web_interface() {
    // 先尝试加载配置文件
    bool flag = load_param();
    if (!flag) {
        // 不存在有效配置文件，直接启动AP 模式
        start_ap();
    }
    else {
        try_connect_wifi();
        // 启动天气和时间更新任务
        delay_callback.add_task(task_update_weather, 6'000);
        delay_callback.add_task(task_update_ntp, 6'000);
    }

    // 无论如何，都得启动服务器
    init_server();
    init_ntp_client();
    // 启动连接状态监视
    delay_callback.add_task(task_wifi_connection_monitor, 12'000);
    // 延时一段时间，显示IP 信息
    delay_callback.add_task(task_show_ip, 12'000);
}